# -*- coding: utf-8 -*-
import datetime
from random import choice

import gevent
from mongoengine import DoesNotExist
from gevent import Greenlet
from gevent import socket
from gevent import queue as Queue
from gevent.event import AsyncResult
from gevent.event import Timeout
import datetime

from core.packet import makepack, Read
from core.model import Device, StarAward, User, getuserdevRelate, Loger, Silent
from agent.alphabet import OK, TIMEOUT
import setting
import instruct

dispatch = {}
for i in dir(instruct):
    try:
        if i.find('_') == -1 and i.startswith('S') and isinstance(int(i[1:]), int):
            dispatch[i] = getattr(instruct, i)
    except ValueError:
        pass

import logging

logger = logging.getLogger('server')
server_recvout = setting.server['recvout']
chars = '0123456789'
audioFragment = setting.server['audioFragment']
babyDefaultName = setting.default['name'].encode('utf8')


def makeIdentify():
    string = [choice(chars) for _ in xrange(15)]
    return ''.join(string)


class DeviceConnect(object):
    __slots__ = (
        '_sock', '_status', 'send_Q', 'recv_Box', '_trigger', 'version',
        'identify', 'imei', 'data', 'data_index', 'model',
        'new_lastRegionId', 'new_voltage', 'new_sos', 'new_jobbox', 'new_audio', 'new_modified', 'new_timezone',
        'new_lasttime', 'new_version'
    )

    def __init__(self, sock, address, version, identify, imei):
        self._sock = sock
        self._status = True
        self.send_Q = Queue.Queue()
        # 指令发送队列
        self.recv_Box = {}
        # 指令接收队列
        self._trigger = False
        # 腕表已在线超过十秒,指令的回复可以通过发送队列进行发送
        self.version = version
        # V1.0.6 or V1.0.7
        self.identify = identify
        # 终端的imsi号,只在S1、S2时发送,其余指令时为随机字符串
        self.imei = imei
        # 相互绑定终端与终端的连接
        self.data = ''
        self.data_index = 0

        try:
            dev = Device.objects(imei=imei).get()
            self.new_lasttime = datetime.datetime.now()
            if version != dev.version:
                self.new_version = version
        except Device.DoesNotExist:
            dev = Device(imei=self.imei)
            s = Silent(num=1, weekday='1,2,3,4,5', starttime=datetime.datetime(1900, 1, 1, 9, 0),
                       endtime=datetime.datetime(1900, 1, 1, 17, 0), status=False)
            dev.silent = [s]
            dev.version = version
            dev.save()

        self.model = dev

    def _handle_send_Q(self):
        """
        发送发送队列中的数据
        """
        gevent.sleep(10)
        # 10s后如果当前连接仍然在线,则开始发送指令
        Greenlet.spawn(self.syncData).start()
        self._trigger = True
        try:
            while self._status:
                some = self.send_Q.get()
                if some:
                    self._sock.sendall(some)
                    gevent.sleep(1.2)
                    # 避免多条指令发送时终端缓冲区在一个socket包中收到多个数据
                else:
                    return None
        except socket.error:
            self.stop()
            return None
        except:
            self.stop()
            logger.error(self.imei, exc_info=True)
            return None

    def start(self):
        Greenlet.spawn(self._handle_send_Q).start()
        try:
            while self._status:
                version, identify, timezone, imei, recv_instruct, params = Read(self._sock)
                self.handle(identify, timezone, imei, recv_instruct, params)
        except socket.error:
            self.stop()
        except:
            logger.error(self.imei, exc_info=True)
            self.stop()

    def handle(self, identify, timezone, imei, recv_instruct, params):
        if recv_instruct not in dispatch:
            logger.error('instruct nofind %s %s %s %r' % (identify, imei, recv_instruct, params))
            return None
        if recv_instruct != 'S52':
            Loger(model='server', ident=imei, event='recv', value='%s %r' % (recv_instruct, params)).save()

        if recv_instruct == 'S1':
            params = timezone

        try:
            pattern, argument = dispatch[recv_instruct](self, params)
        except DoesNotExist:
            self.model = None
            self.stop()
            return None

        if pattern == 'send':
            Loger(model='server', ident=imei, event='send', value='%s %r' % (recv_instruct, argument)).save()
            if self._trigger:
                self.send(recv_instruct, argument, identify=identify, timezone=timezone)
            else:
                # 腕表在10秒钟前,需要对腕表发过来的指令进行快速回复
                self._sock.sendall(makepack(self.version, identify, imei, recv_instruct, argument, timezone))
        if pattern == 'done':
            if identify in self.recv_Box:
                self.recv_Box[identify].set('ok')
        if pattern == 'stop':
            self.stop()

    def send(self, send_instruct, params=None, identify=None, interact=None, timezone=8):
        if send_instruct == 'S8':
            timezone = int(params[0])

        if send_instruct == 'S98':
            # 删除腕表指令
            self.model = None
            self.stop()
            return None

        if send_instruct == 'S99':
            # 刷新model对象
            # self.model.save()
            self.model.reload()
            return None

        if send_instruct == 'S19':
            if self.version == 'V1.0.6':
                params = [params[0]]
            else:
                send_instruct = 'S23'
                whiltelist = params[0].split(';')
                namelist = params[1].split(';')
                params = ['%s,|%s|' % (white, namelist[i]) for i, white in enumerate(whiltelist) if white]
                padding = 10 - len(params)
                params = [';'.join(params) + ';' * padding]

        if send_instruct == 'S20':
            if self.version == 'V1.0.6':
                params = [params[0]]
            else:
                send_instruct = 'S24'
                whiltelist = params[0].split(';')
                namelist = params[1].split(';')
                params = ['%s,|%s|' % (white, namelist[i]) for i, white in enumerate(whiltelist) if white]
                padding = 10 - len(params)
                params = [';'.join(params) + ';' * padding]

        if interact:
            identify = makeIdentify()
            self.recv_Box[identify] = AsyncResult()
            if len(self.recv_Box) > 50:
                oldkey = []
                for ident in self.recv_Box:
                    try:
                        self.recv_Box[ident].get_nowait()
                        oldkey.append(ident)
                    except Timeout:
                        pass
                for old_ident in oldkey:
                    del self.recv_Box[old_ident]

        if not identify:
            identify = self.identify

        self.send_Q.put(makepack(self.version, identify, self.imei, send_instruct, params, timezone))
        return identify

    def interact(self, send_instruct, params):
        """
        向终端发送数据
        instruct  <str>  指令编码  '\x01'
        params    <str>  参数  ['on','1']
        """

        Loger(model='server', ident=self.imei, event='send', value='%s %r' % (send_instruct, params)).save()

        identify = self.send(send_instruct, params, interact=True)
        # interact渠道不带identify参数,由send生成后在recv_Box中获取AsyncResult结果
        try:
            self.recv_Box[identify].get(timeout=server_recvout)
            return OK
        except Timeout:
            return TIMEOUT

    def syncData(self):
        dev = self.model

        # 每次腕表上线时同步离线数据
        if dev.jobbox:
            jobbox = []
            pending_job = {}
            for job in dev.jobbox:
                if job.instruct in ('S19', 'S20', 'S41', 'S16'):
                    pending_job[job.instruct] = job
                else:
                    params = job.params.split(',') if job.params else []
                    result = self.interact(job.instruct, params)
                    if result != OK:
                        jobbox.append(job)
            for job in pending_job:
                params = job.params.split(',') if job.params else []
                result = self.interact(job.instruct, params)
                if result != OK:
                    jobbox.append(job)
            self.new_jobbox = jobbox

        if dev.audio:
            success_audio = []
            for audio in dev.audio:
                content = audio.content.read()
                l = str(len(content))
                status = self.interact('S53', ['START', l])
                if status != OK:
                    break
                i = 0
                j = audioFragment
                k = 0
                while True:
                    data = content[i:j]
                    if data:
                        status = self.interact('S53', ['DOWN', str(k), '\xab\xab\xab' + data + '\xab\xab\xab'])
                        if status != OK:
                            break
                        k += 1
                    else:
                        break
                    i += audioFragment
                    j += audioFragment
                status = self.interact('S53', ['END', l])
                if status != OK:
                    break
                audio.content.delete()
                success_audio.append(audio)
            for audio in success_audio:
                dev.audio.remove(audio)
            self.new_audio = dev.audio

        # 腕表有待同步的数据时同步数据
        if not dev.modified:
            return None

        num = 0

        sex = '1' if dev.sex == 'boy' else '0'
        age = str(dev.age) if dev.age else setting.default['age']
        height = str(dev.height) if dev.height else setting.default['height']
        weight = str(dev.weight) if dev.weight else setting.default['weight']
        steplen = str(dev.steplen) if dev.steplen else setting.default['steplen']

        if dev.name:
            j = 0
            name = ''
            for i in unicode(dev.name):
                if i.isalpha():
                    j += 1
                else:
                    j += 2
                if j > 8:
                    break
                name += i.encode('utf8')
        else:
            name = babyDefaultName
        status = self.interact('S45', [age, height, steplen, weight, sex, '|' + name + '|'])
        if status != OK:
            num += 1
        status = self.interact('S16', ['00:00:00', '23:59:59', str(dev.localintervalminute)])
        if status != OK:
            num += 1

        result = StarAward.objects(dev=dev, done=False)
        if not result:
            status = self.interact('S50', ['0'])
        else:
            staraward = result[0]
            status = self.interact('S50', [str(staraward.star)])
            if len(result) > 1:
                for a in result[1:]:
                    a.delete()
        if status != OK:
            num += 1

        if dev.whitelist:
            if self.version == 'V1.0.6':
                padding = 10 - len(dev.whitelist)
                whitestring = str(';'.join(dev.whitelist) + ';' * padding)
                status = self.interact('S19', [whitestring, ''])
                if status != OK:
                    num += 1
                status = self.interact('S20', [whitestring, ''])
                if status != OK:
                    num += 1
            else:
                username = {}
                for u in User.objects(id__in=dev.family).only('phone', 'relateInfo'):
                    username[u.phone] = getuserdevRelate(u.relateInfo).encode('utf8')
                whitelist = []
                namelist = []
                overname = {}
                for phone in dev.whitelist:
                    try:
                        name = username[phone]
                    except KeyError:
                        name = '成员'
                    if name not in overname:
                        overname[name] = 1
                    else:
                        overname[name] += 1
                        name = name + str(overname[name])
                    whitelist.append(str(phone))
                    namelist.append(name)
                whitestring = ';'.join(whitelist)
                namestring = ';'.join(namelist)
                status = self.interact('S19', [whitestring, namestring])
                if status != OK:
                    num += 1
                status = self.interact('S20', [whitestring, namestring])
                if status != OK:
                    num += 1
        else:
            s = ';' * 10
            if self.version == 'V1.0.6':
                status = self.interact('S19', [s, ''])
                if status != OK:
                    num += 1
                status = self.interact('S20', [s, ''])
                if status != OK:
                    num += 1
            else:
                status = self.interact('S19', [s, s])
                if status != OK:
                    num += 1
                status = self.interact('S20', [s, s])
                if status != OK:
                    num += 1
        if num == 0:
            # dev.modified = 0
            self.new_modified = 0

    def update_model(self):
        modify = {}
        for attr in (
                'voltage', 'sos', 'modified', 'jobbox', 'audio', 'lastRegionId', 'timezone', 'lasttime', 'version'):
            if getattr(self, 'new_%s' % attr, None) is not None:
                modify[attr] = getattr(self, 'new_%s' % attr)
        if modify:
            Device.objects(imei=self.imei).update_one(**modify)

    def stop(self):
        """
        self._sock.shutdown 有可能会失败,需要放到最后执行
        """
        if self._status:
            self._status = False
            if self.model:
                self.update_model()
            self.send_Q.put('')
            self._sock.shutdown(socket.SHUT_RDWR)
            self._sock.close()

    def __del__(self):
        self.stop()

    def __repr__(self):
        return '<CONNECT %r>' % self.imei

    def __str__(self):
        return '<CONNECT %r>' % self.imei
